﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;

namespace ConsoleApplication1
{
    static class TypeBuilderHelper
    {
        public static AssemblyBuilder Assembly => AppDomain.CurrentDomain.DefineDynamicAssembly(AssemblyName,
                AssemblyBuilderAccess.Run);

        public static AssemblyName AssemblyName => new AssemblyName("_Dynamic");

        public static ModuleBuilder Module => Assembly.DefineDynamicModule("_Dynamic");

        private readonly static ConcurrentDictionary<Type, DynamicActivator> propertyGetterCache = new ConcurrentDictionary<Type, DynamicActivator>();

        public static NamedArgument[] GetProperties(object instance)
        {
            var activator = propertyGetterCache.GetOrAdd(instance.GetType(), key =>
                CreateActivator(instance));

            return activator.Invoke(instance);
        }

        private static readonly ConstructorInfo NamedArgument = typeof(NamedArgument).GetConstructor(new Type[] { typeof(Type), typeof(string), typeof(object) });
        private static readonly MethodInfo GetTypeFromHandle = typeof(Type).GetMethod("GetTypeFromHandle");
        private static DynamicActivator CreateActivator(object instance)
        {
            var type = instance.GetType();
            var builder = Module.DefineType($"{type.Name}Activator", TypeAttributes.Public, typeof(DynamicActivator));
            var method = builder.DefineMethod("Invoke", MethodAttributes.Virtual | MethodAttributes.Public | MethodAttributes.Final, typeof(NamedArgument[]), new Type[] { typeof(object) });
            var il = method.GetILGenerator();

            var properties = type.GetProperties();

            var local = il.DeclareLocal(typeof(NamedArgument[]));
            il.EmitInt(properties.Length);
            il.Emit(OpCodes.Newarr, typeof(NamedArgument));
            il.Emit(OpCodes.Stloc_0);

            for (var i = 0; i < properties.Length; i++)
            {
                var property = properties[i];
                il.Emit(OpCodes.Ldloc_0);
                il.EmitInt(i);
                il.Emit(OpCodes.Ldtoken, property.PropertyType);
                il.Emit(OpCodes.Call, GetTypeFromHandle);
                il.Emit(OpCodes.Ldstr, property.Name);
                il.Emit(OpCodes.Ldarg_1);
                il.Emit(OpCodes.Callvirt, property.GetGetMethod());
                if (property.PropertyType.IsValueType)
                    il.Emit(OpCodes.Box, property.PropertyType);
                il.Emit(OpCodes.Newobj, NamedArgument);
                il.Emit(OpCodes.Stelem, typeof(NamedArgument));
            }

            il.Emit(OpCodes.Ldloc_0);
            il.Emit(OpCodes.Ret);

            var activatorType = builder.CreateType();

            return (DynamicActivator)Activator.CreateInstance(activatorType);
        }
    }


    public abstract class DynamicActivator
    {
        public abstract NamedArgument[] Invoke(object obj);
    }
    public class xxx : DynamicActivator
    {
        public override NamedArgument[] Invoke(object obj)
        {
            throw new NotImplementedException();
        }
    }

}
